package edu.ccdb; 

import java.awt.Component;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Vector;

import javax.swing.*;

import edu.ccdb.irods.Configuration;
import edu.ccdb.irods.IRODSOperations;
import edu.ccdb.irods.InstanceInfo;
import edu.ccdb.irods.SQLQuery;
import edu.ccdb.irods.dataType.DataValue;
import edu.ccdb.irods.dataType.IRODSFileString;
import edu.ccdb.irods.dataType.PurposeValue;
import edu.ccdb.irods.methods.UploadToolFileStatus;
import edu.ccdb.irods.signature.SwingWorkerExtension;
import edu.ccdb.util.BareBonesBrowserLauncher;
import edu.sdsc.grid.io.irods.IRODSAccount;

import edu.ucsd.ccdb.uploadV4.dataModel.CCDBFile;


public class Uploader extends JFrame implements ActionListener {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	final JFileChooser fc = new JFileChooser();
	private String hostAndPortNumber ; // for submitter
	
	private JTextField filePathTxt;
	
	private InstanceInfo info;
	
	private JProgressBar progressBar;
	private JLabel status ;
	private JButton uploadButton ;
	private JTextArea taskOutput; 
	private String baseFolder;	
		
	private Vector <CCDBFile> logFileUploaded;
	
	private String[] args;
	
	public Vector<CCDBFile> getLogFileUploaded() {
		return logFileUploaded;
	}
	
	private String getExtension ( String fullName ){
		
		if ( fullName.lastIndexOf('.') != -1 )
			return fullName.substring(fullName.lastIndexOf('.') + 1);
		return "";
		
	}
	
	/*
	 * This method use the the parameter avoidFileExtensions to filter out unwanted files
	 * 
	 * @param avoidFileExtensions	contains a list of file extensions
	 * @param input_folder	the absolute path of the folder to be inspected
	 */
	public Vector <CCDBFile> listIRODSFolder (String input_folder, Vector<String> avoidFileExtensions ){
		Vector <CCDBFile> result = new Vector<CCDBFile>();
		
		for (CCDBFile element: logFileUploaded){
			
			String ext = getExtension ( element.getPath() );
			String elementPath = element.getPath().replace( '\\','/' );
			
			if ( elementPath.matches(input_folder+".+") ) {
				if (! avoidFileExtensions.contains(ext) ){
					result.add(element);
				}
			}
		}
		return result;
	}

	public Uploader ( String[] args) {
		
		final File homeDirectory;
		
		this.args = args;
		
		if (args.length != 5) {
			
			JOptionPane.showMessageDialog(this, "Not Enough Argument to launch upload tool",
					"Error Launch", JOptionPane.ERROR_MESSAGE);
			return;
		}
		
		if (!args[1].endsWith("/"))
			hostAndPortNumber = args[1] + "/";
		else hostAndPortNumber = args[1];
		
		
		// initialization GUI
		JComponent cont = (JComponent) getContentPane();
		cont.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
		
		// set up session first		
		String sessionID = args[0];
		info = Submitter.setUpSession( sessionID, hostAndPortNumber ); 
		baseFolder = info.getPath();
		//baseFolder = Configuration.BASEFOLDER;
		
		// set up the top header
		setTitle ("Upload Tool V1.4");
		try {
			Integer MPID = info.getMPID() ; 
			PurposeValue purposeValue = SQLQuery.getPurposeValue( Integer.parseInt( args[2] ) );
			List<DataValue> dataValueList = SQLQuery.getDataSectionID(args[4] , Integer.parseInt(args[3]), MPID );
			
			DataValue dataValue = null;
			
			for (DataValue element: dataValueList) {
				if (purposeValue.getType() == element.getType() )
					dataValue = element;
			}
			
			if (dataValue != null ) {
				setTitle( "Upload tool v1.4" + " - Tagging [" + purposeValue + " - " + dataValue + "]" );
			}
		} 
		catch (Exception e) {
			e.printStackTrace();
		}
		
		logFileUploaded = new Vector<CCDBFile>();
	
		setResizable(false);
		
		//initialize the jfilechooser, read profile in home, if any		
//		fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
		fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
		homeDirectory = fc.getCurrentDirectory();	// initially the current directory is the home page
		
		File profile = new File(homeDirectory + File.separator + Configuration.PROFILENAME );
		
		
		// read the profile, then set current directory
		try {
			FileReader fr = new FileReader(profile);
			BufferedReader br = new BufferedReader(fr);
			
			String line = br.readLine();
			File directoryFromFile = null;
			if ( line != null ) {
				directoryFromFile = new File ( line );
			}
			
			System.out.println ("Directory in file: " + line );
			if ( directoryFromFile == null || directoryFromFile.exists() ) {		
				fc.setCurrentDirectory ( directoryFromFile );				
			}
			
			fr.close();
		}
		catch (FileNotFoundException e1) {
			System.err.println ("No profile found");
		} catch (IOException e) {
			e.printStackTrace();
			System.err.println ("File error");
		}
		
		// top pane 1 component
		JLabel localTxt = new JLabel("Local: ");
		
		filePathTxt = new JTextField(40);
		filePathTxt.setEditable(false);
		
		JButton browseFile = new JButton("Browse");
		browseFile.addActionListener(this);

		// top pane 2 components
		JLabel remoteTxt = new JLabel ("Remote: ");
		
		JTextField filePathRemoteTxt = new JTextField(baseFolder, 46);
		filePathRemoteTxt.setEditable(false);
		
		// middle pane component
		progressBar = new JProgressBar(0, 100);
		progressBar.setValue(0);
		progressBar.setStringPainted(true);
		
		status = new JLabel("Ready");
		status.setAlignmentX(Component.CENTER_ALIGNMENT);

		// bottom pane component
		uploadButton = new JButton("Upload");
		uploadButton.addActionListener( new uploadListener() );
		
		taskOutput = new JTextArea(9, 30);
		taskOutput.setMargin(new Insets(10,5,10,5));
		taskOutput.setEditable(true);
		taskOutput.setAlignmentX(Component.CENTER_ALIGNMENT);
		
		// add layout and component to this frame
		JPanel topPane1 = new JPanel();
		JPanel topPane2 = new JPanel ();
		JPanel bottomPane = new JPanel();
		JPanel middlePane = new JPanel();

		middlePane.setLayout(new BoxLayout(middlePane, BoxLayout.Y_AXIS)) ;
		
		topPane1.add(localTxt);
		topPane1.add(filePathTxt);	// name "remote" on the GUI 
		topPane1.add(browseFile);
		topPane2.add(remoteTxt);
		topPane2.add(filePathRemoteTxt);
		
		middlePane.add(progressBar);
		middlePane.add(status);

		bottomPane.add(uploadButton);
//		bottomPane.add(new JScrollPane(taskOutput, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER) );
		
		JLabel reportBugLink = new JLabel ("<html><font color=blue><u>Report bugs</u></font></html>");
		reportBugLink.setToolTipText("We'll love you forever for doing this :D " );
		reportBugLink.addMouseListener(new ReportBugMouseListener());
		reportBugLink.setAlignmentX(CENTER_ALIGNMENT);
		
		getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));
		add (topPane1);
		add (topPane2);
		add (middlePane);
		add (bottomPane);
		add(new JScrollPane(taskOutput, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER) );
		add (reportBugLink);
		
		
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		pack();
		setVisible(true);
		
		// on close, save the profile
		addWindowListener( new WindowAdapter() {
			public void windowClosing (WindowEvent e){
				
				try {
					FileWriter writer = new FileWriter( homeDirectory.getPath() + File.separator + Configuration.PROFILENAME );	// write to home directory 
					BufferedWriter out = new BufferedWriter(writer);

					if ( fc.getSelectedFile()!= null ) // if the selected file is valid (or the user ever select a file)
						out.write ( fc.getSelectedFile().getPath() );
					out.close();
				}
				catch ( Exception ex) {
					ex.printStackTrace();
				}
				System.exit(0);
			}
		});
	}
	
	/* 
	 * (non-Javadoc) This action listener will set the text on remote tab. Don't change this :D 
	 * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
	 */
	public void actionPerformed(ActionEvent arg0) {

		int val = fc.showOpenDialog(Uploader.this);
		
		if ( val == JFileChooser.APPROVE_OPTION ){
			File file = fc.getSelectedFile();
			filePathTxt.setText(file.getPath());
		}		
	}
	
	private String getRelativePath (String base, String target) {
		if ( base.endsWith(File.separator) )
			return target.replace( base, "");
		else 
			return target.replace(base + File.separator, "");
	}
	
	private class uploadListener implements ActionListener, PropertyChangeListener {
		lengthyUploadThread task ;
		
		public void actionPerformed(ActionEvent e) {
			// reset the progressbar to 0
			progressBar.setValue(0);
			
			task = new lengthyUploadThread();
			task.addPropertyChangeListener(this);
			task.execute();
		}
		
		public void propertyChange(PropertyChangeEvent arg0) {
			// get jprogressbar status here 
			if ( arg0.getPropertyName() == "progress" ) {
				progressBar.setValue(task.getProgress());
				status.setText( task.getText() );
			}
		}
	}
	
	private class lengthyUploadThread extends SwingWorkerExtension {

		private long transferedSize;
		private BigInteger totalSizeToCopy;
		private IRODSAccount account ;
		UploadToolFileStatus statusDetail;
		
		List<String> listFile = new LinkedList<String>();
		
		boolean isSuccess = true;
		
		// depth first search to find a size of a folder
		private BigInteger getDirSize(File dir) {
			BigInteger size = new BigInteger("0");
			if (dir.isFile()) {
				size = new BigInteger( String.valueOf( dir.length() ) );
			}
			else {
				File[] subFiles = dir.listFiles();

				for (File file : subFiles) {
					if (file.isFile()) {
//						size += file.length();
//						size.add( new BigInteger( String.valueOf( file.length() ) ) );
						size = size.add(  BigInteger.valueOf( file.length() )  );
						
						//extra, for getting the list of all files
						listFile.add(file.getAbsolutePath());
					} 
					else {
//						size += this.getDirSize(file);
						size = size.add( this.getDirSize(file) );
					}	
				}
			}
			return size;
		}
		
		@Override
		protected Void doInBackground() throws Exception {
			
			// GUI initialize
			uploadButton.setEnabled(false);

			// try to upload file, using breath first search
			status.setText( "Calculating size" );
			totalSizeToCopy = getDirSize ( fc.getSelectedFile() );	// work for both file and directory

			Queue<File> queue = new LinkedList<File>();
			queue.add(fc.getSelectedFile());
			
			// for the property change event
			setProgress(0);
			
			statusDetail = new UploadToolFileStatus( listFile, totalSizeToCopy );
			
			// check if the base folder exists
			if (! IRODSOperations.isExist (baseFolder) ) {
//				IRODSOperations.createInitalDirectory(baseFolder);
				IRODSOperations.createNewInitalDirectory(baseFolder);
			}

			long start = System.currentTimeMillis();	// for calculate performance
			
			account = IRODSOperations.createNewIRODSAccount(baseFolder);
			transferedSize = 0;
			
			boolean isUploadSingleFile = fc.getSelectedFile().isFile(); 
			
			// get all data before upload :)
			Integer MPID = info.getMPID() ; 
			PurposeValue purposeValue = SQLQuery.getPurposeValue( Integer.parseInt( args[2] ) );
			List<DataValue> dataValueList = SQLQuery.getDataSectionID(args[4] , Integer.parseInt(args[3]), MPID );
			
			DataValue dataValue = null;
			
			for (DataValue element: dataValueList) {
				if (purposeValue.getType() == element.getType() )
					dataValue = element;
			}
			
			try {
				while (!queue.isEmpty() ) {
					File child = queue.poll();
					
					if ( child.isDirectory() ) {// means it a directory
						// create the directory 					
						IRODSOperations.createDir (baseFolder, 
								getRelativePath(fc.getSelectedFile().getParent(), child.getAbsolutePath() ) );
						
						File [] listFile = child.listFiles();
						queue.addAll( Arrays.asList(listFile) );
					}
					else { // a regular file
						transferedSize += child.length();
						
//						System.err.println ( (int) transferedSize + "/" + (int) totalSizeToCopy.intValue());
//						System.err.println ( (int) (transferedSize * 100/ totalSizeToCopy.intValue() )   );

						taskOutput.append( String.format("Uploading \"%s\"\n", child.getName() ) );
						taskOutput.setCaretPosition(taskOutput.getDocument().getLength());

						String relativePath = getRelativePath(fc.getSelectedFile().getParent(), child.getAbsolutePath() );
						IRODSOperations.uploadFile(  relativePath , child , account, statusDetail, this);
						
						// report to the GUI, signal on the uploadFile function, perform on other function
//						setText( "Uploading " + child.getName() );
//						setProgress( (int) (transferedSize * 100/ totalSizeToCopy )   );
						taskOutput.append( String.format("\"%s\" completed.\n", child.getName() ) );
						taskOutput.setCaretPosition(taskOutput.getDocument().getLength());
						
						// update the logFile						
						CCDBFile file = new CCDBFile();
						file.setPath(account.getHomeDirectory() + "/" + relativePath.replace(File.separator, "/"));
						file.setSize( child.length() );
						logFileUploaded.add( file );
						
						//TODO finish inserting CCDB tags here -- DEBUG
						/*
						 * MPID : info.getMPID
						 * args[2]: purposeID 
						 * args[3]:  username
						 * args[4]:  reconstruction ID 
						 */
						
//						if (isUploadSingleFile) {
//							Integer MPID = info.getMPID() ; 
//							PurposeValue purposeValue = SQLQuery.getPurposeValue( Integer.parseInt( args[2] ) );
//							List<DataValue> dataValueList = SQLQuery.getDataSectionID(args[4] , Integer.parseInt(args[3]), MPID );
//							
//							DataValue dataValue = null;
//							
//							for (DataValue element: dataValueList) {
//								if (purposeValue.getType() == element.getType() )
//									dataValue = element;
//							}							
							IRODSFileString CCDBfile = new IRODSFileString(file.getPath(), child.getName() , false);
							
							if ( dataValue != null )	// if not valid then no tag
								SQLQuery.manageInsertCCDBTag(MPID, CCDBfile, purposeValue, dataValue);
//						}
					}
				}
			}
			catch (IOException ex){
				ex.printStackTrace();
				JOptionPane.showMessageDialog ( Uploader.this, ex.getClass(), "IOException", JOptionPane.ERROR_MESSAGE );
				
				isSuccess = false;
			}
			catch (Exception ex) {
				ex.printStackTrace();
				JOptionPane.showMessageDialog ( Uploader.this, ex.getClass(), "Exception", JOptionPane.ERROR_MESSAGE );
				
				isSuccess = false;
			}
			
			long end = System.currentTimeMillis();
			System.out.print ("Time taken: " + (end - start) / (double) 1000 );
			
			return null;
		}

		@Override 
		public void done () {
			
			// beep
			Toolkit.getDefaultToolkit().beep();
			uploadButton.setEnabled(true);
			taskOutput.append("Done!\n");
			
			// in event dispatching thread
			progressBar.setValue(100);
			status.setText("Finished");
			
			for (CCDBFile element: getLogFileUploaded() ){
				System.out.println ( element.getPath() + " " + element.getSize() );
			}
			
			// check if there is any error before submitting files
			
			if (isSuccess) {
			
				Integer imgageID = Submitter.submitInfoAboutUploadedFiles(  getLogFileUploaded(), info.getMPID(), hostAndPortNumber  );
				JOptionPane.showMessageDialog( Uploader.this, "Completed successfully.\nNew Image ID: " + imgageID, 
							"Upload completed", JOptionPane.INFORMATION_MESSAGE );
			}
			
			logFileUploaded = new Vector<CCDBFile>();	// reset the log file, prevent duplicates documents
		}
	}

	private class ReportBugMouseListener implements MouseListener {
		public void mouseClicked(MouseEvent e) {
			BareBonesBrowserLauncher.openURL("http://code.google.com/p/irods-uploader/issues/entry");
		}

		public void mouseEntered(MouseEvent e) {			
		}

		public void mouseExited(MouseEvent e) {			
		}

		public void mousePressed(MouseEvent e) {			
		}

		public void mouseReleased(MouseEvent e) {			
		}
	}
	
	/**
	 * Main function for testing 
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		
//		Uploader upload = new Uploader(args[0] );
		new Uploader(args );
	}
}
